package com.leyou.search.service;

import com.leyou.common.dto.PageResult;
import com.leyou.common.enums.ExceptionEnum;
import com.leyou.common.exceptions.LyException;
import com.leyou.common.utils.BeanHelper;
import com.leyou.common.utils.JsonUtils;
import com.leyou.item.client.ItemClient;
import com.leyou.item.dto.*;
import com.leyou.search.dto.GoodsDTO;
import com.leyou.search.dto.SearchRequest;
import com.leyou.search.pojo.Goods;
import com.leyou.search.repository.GoodsRepository;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @author 虎哥
 */
@Service
public class SearchService {

    @Autowired
    private ItemClient itemClient;

    public Goods buildGoods(SpuDTO spu) {
        // 1.包含：标题、分类名称、品牌名称等
        // 1.1.处理分类名称
        String categoryName = spu.getCategoryName();
        if (StringUtils.isBlank(categoryName)) {
            categoryName = itemClient.queryCategoryByIds(spu.getCategoryIds())
                    .stream()
                    .map(CategoryDTO::getName)
                    .collect(Collectors.joining());
        }
        // 1.2.品牌名称
        String brandName = spu.getBrandName();
        if (StringUtils.isBlank(brandName)) {
            BrandDTO brand = itemClient.queryBrandById(spu.getBrandId());
            brandName = brand.getName();
        }
        String all = spu.getName() + categoryName + brandName;

        // 2.商品sku的集合
        List<SkuDTO> skuDTOList = spu.getSkus();
        if (CollectionUtils.isEmpty(skuDTOList)) {
            skuDTOList = itemClient.querySkuBySpuId(spu.getId());
        }
        List<Map<String, Object>> skuList = new ArrayList<>();
        for (SkuDTO skuDTO : skuDTOList) {
            Map<String, Object> sku = new HashMap<>();
            sku.put("id", skuDTO.getId());
            sku.put("title", skuDTO.getTitle());
            sku.put("price", skuDTO.getPrice());
            sku.put("image", StringUtils.substringBefore(skuDTO.getImages(), ","));
            skuList.add(sku);
        }

        // 3.商品sku的价格集合
        Set<Long> prices = skuDTOList.stream().map(SkuDTO::getPrice).collect(Collectors.toSet());

        // 4.商品规格参数的key和value, key是当前商品要参与搜索的规格参数的名称、值是规格参数的值
        Map<String, Object> specs = new HashMap<>();
        // 4.1.规格参数的key, 当前商品所属分类的要参与搜索的规格参数的名称
        List<SpecParamDTO> params = itemClient.querySpecParams(null, spu.getCid3(), true);
        // 4.2.规格参数的value, 存在于spuDetail表中
        SpuDetailDTO spuDetail = spu.getSpuDetail();
        if (spuDetail == null) {
            spuDetail = itemClient.querySpuDetailById(spu.getId());
        }
        // 4.2.1.取出通用的规格值
        Map<Long, Object> genericSpec = JsonUtils.toMap(spuDetail.getGenericSpec(), Long.class, Object.class);
        // 4.2.2.取出特有的规格值
        Map<Long, Object> specialSpec = JsonUtils.toMap(spuDetail.getSpecialSpec(), Long.class, Object.class);
        // 合并值
        genericSpec.putAll(specialSpec);
        // 4.3.组装specs,把key和value配对
        for (SpecParamDTO param : params) {
            // 取出参数名称，作为specs的key
            String key = param.getName();
            // 取出value
            Object value = genericSpec.get(param.getId());
            // 判断value是否是数字类型， 如果是，判断在哪个数值范围，存入索引库的是范围值，而不是具体值
            if (param.getNumeric()) {
                // 处理value，把数字变成范围的字符串
                value = chooseSegment(value, param);
            }
            // 保存
            specs.put(key, value);
        }

        // 创建goods，封装数据
        Goods goods = new Goods();
        goods.setSubTitle(spu.getSubTitle());
        goods.setId(spu.getId());
        goods.setCategoryId(spu.getCid3());
        goods.setBrandId(spu.getBrandId());
        // 包含：标题、分类名称、品牌名称等。。。
        goods.setAll(all);
        goods.setCreateTime(System.currentTimeMillis());
        // 商品sku的集合
        goods.setSkus(JsonUtils.toString(skuList));
        // 商品sku的价格集合
        goods.setPrice(prices);
        // 商品规格参数的key和value
        goods.setSpecs(specs);
        return goods;
    }

    private Object chooseSegment(Object value, SpecParamDTO p) {
        if (value == null || StringUtils.isBlank(value.toString())) {
            return "其它";
        }
        double val = 0;
        try {
            val = parseDouble(value.toString());
        } catch (Exception e) {
            return value;
        }
        String result = "其它";
        // 保存数值段
        for (String segment : p.getSegments().split(",")) {
            String[] segs = segment.split("-");
            // 获取数值范围
            double begin = parseDouble(segs[0]);
            double end = Double.MAX_VALUE;
            if (segs.length == 2) {
                end = parseDouble(segs[1]);
            }
            // 判断是否在范围内
            if (val >= begin && val < end) {
                if (segs.length == 1) {
                    result = segs[0] + p.getUnit() + "以上";
                } else if (begin == 0) {
                    result = segs[1] + p.getUnit() + "以下";
                } else {
                    result = segment + p.getUnit();
                }
                break;
            }
        }
        return result;
    }

    private double parseDouble(String str) {
        return Double.parseDouble(str);
    }

    @Autowired
    private ElasticsearchTemplate template;

    public PageResult<GoodsDTO> search(SearchRequest request) {
        // 1.初始化原生查询构建器
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();

        // 2.构建搜索条件
        // 2.1.source过滤
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{"id", "subTitle", "skus"}, new String[0]));
        // 2.2.查询条件：关键字搜索
        String key = request.getKey();
        if(StringUtils.isBlank(key)){
            // 如果用户不输入关键字，返回404
            throw new LyException(ExceptionEnum.RESOURCE_NOT_FOUND);
        }
        QueryBuilder basicQuery = buildBasicQuery(request);
        queryBuilder.withQuery(basicQuery);
        // 2.3.分页
        int page = request.getPage() - 1;
        queryBuilder.withPageable(PageRequest.of(page, request.getSize()));
        // 2.4.排序
        String sortBy = request.getSortBy();
        if(StringUtils.isNotBlank(sortBy)){
            SortOrder order = request.getDesc() ? SortOrder.DESC : SortOrder.ASC;
            queryBuilder.withSort(SortBuilders.fieldSort(sortBy).order(order));
        }

        // 3.查询数据
        AggregatedPage<Goods> result = template.queryForPage(queryBuilder.build(), Goods.class);

        // 4.解析数据
        // 4.1.分页数据
        long total = result.getTotalElements();
        int totalPages = result.getTotalPages();
        List<Goods> content = result.getContent();
        // 4.2.处理DTO
        List<GoodsDTO> list = BeanHelper.copyWithCollection(content, GoodsDTO.class);

        return new PageResult<>(total, totalPages, list);
    }

    private QueryBuilder buildBasicQuery(SearchRequest request) {
        // 1.创建布尔查询
        BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
        // 2.添加must条件
        queryBuilder.must(QueryBuilders.matchQuery("all", request.getKey()));
        // 3.添加filter条件
        Map<String, String> filters = request.getFilter();
        for (Map.Entry<String, String> entry : filters.entrySet()) {
            // 过滤项的名称
            String key = entry.getKey();
            if("分类".equals(key)){
                key = "categoryId";
            }else if("品牌".equals(key)){
                key = "brandId";
            }else{
                key = "specs." + key;
            }
            // 添加过滤条件
            queryBuilder.filter(QueryBuilders.termQuery(key, entry.getValue()));
        }
        return queryBuilder;
    }

    public Map<String, List<?>> getFilters(SearchRequest request) {
        // 1.初始化原生查询构建器
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();

        // 2.构建搜索条件
        buildNativeSearchQueryBuilder(request, queryBuilder);

        // 3.添加聚合条件
        queryBuilder.addAggregation(AggregationBuilders.terms("categoryAgg").field("categoryId"));
        queryBuilder.addAggregation(AggregationBuilders.terms("brandAgg").field("brandId"));

        // 4.发起请求，查询数据
        AggregatedPage<Goods> result = template.queryForPage(queryBuilder.build(), Goods.class);

        // 5.解析聚合结果
        Aggregations aggregations = result.getAggregations();

        // 5.0.准备一个map，保存查询到过滤项
        Map<String, List<?>> filters = new LinkedHashMap<>();

        // 5.1.获取分类的聚合结果
        // 1)根据名称获取聚合结果
        Terms categoryAgg = aggregations.get("categoryAgg");
        // 2）解析聚合结果，获取id的集合
        List<Long> categoryIdList = getIdsFromAggregation(categoryAgg);
        // 3）根据id查询对象的集合
        List<CategoryDTO> categoryList = itemClient.queryCategoryByIds(categoryIdList);
        // 4）放到map中去
        filters.put("分类", categoryList);

        // 5.2.获取匹配的聚合结果
        // 1)根据名称获取聚合结果
        Terms brandAgg = aggregations.get("brandAgg");
        // 2）解析聚合结果，获取id的集合
        List<Long> brandIdList = getIdsFromAggregation(brandAgg);
        // 3）根据id查询对象的集合
        List<BrandDTO> brandList = itemClient.queryBrandByIds(brandIdList);
        // 4）放到map中去
        filters.put("品牌", brandList);

        // 6.处理规格参数聚合
        if(categoryIdList != null && categoryIdList.size() == 1){
            // 分类只有一个，此时才可以查询规格参数的聚合
            getSpecAggregation(categoryIdList.get(0), request, filters);
        }
        return filters;
    }

    private void buildNativeSearchQueryBuilder(SearchRequest request, NativeSearchQueryBuilder queryBuilder) {
        // 2.1.source过滤
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{""}, new String[0]));
        // 2.2.查询条件：关键字搜索
        String key = request.getKey();
        if(StringUtils.isBlank(key)){
            // 如果用户不输入关键字，返回404
            throw new LyException(ExceptionEnum.RESOURCE_NOT_FOUND);
        }
        QueryBuilder basicQuery = buildBasicQuery(request);
        queryBuilder.withQuery(basicQuery);
        // 2.3.分页信息，减少数据
        queryBuilder.withPageable(PageRequest.of(0, 1));
    }

    private void getSpecAggregation(Long cid, SearchRequest request, Map<String, List<?>> filters) {
        // 0.根据分类id，查询分类下需要参与搜索的规格参数
        List<SpecParamDTO> params = itemClient.querySpecParams(null, cid, true);

        // 1.初始化原生查询构建器
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();

        // 2.构建搜索条件
        buildNativeSearchQueryBuilder(request, queryBuilder);

        // 3.准备聚合条件
        for (SpecParamDTO param : params) {
            // 获取参数名称，作为聚合名称
            String name = param.getName();
            queryBuilder.addAggregation(AggregationBuilders.terms(name).field("specs." + name));
        }

        // 4.开始查询
        AggregatedPage<Goods> result = template.queryForPage(queryBuilder.build(), Goods.class);

        // 5.解析聚合结果
        Aggregations aggregations = result.getAggregations();

        // 遍历参数，逐个取出聚合结果
        for (SpecParamDTO param : params) {
            // 获取参数名称，作为聚合名称
            String name = param.getName();
            // 根据名称获取聚合结果
            Terms terms = aggregations.get(name);
            // 从结果中获取桶，就是待选项的集合
            List<String> list = terms.getBuckets()
                    .stream()
                    .map(Terms.Bucket::getKeyAsString)
                    .filter(StringUtils::isNotBlank)
                    .collect(Collectors.toList());
            // 放入结果map
            filters.put(name, list);
        }
    }

    private List<Long> getIdsFromAggregation(Terms categoryAgg) {
        return categoryAgg.getBuckets()
                .stream()
                .map(Terms.Bucket::getKeyAsNumber)
                .map(Number::longValue)
                .collect(Collectors.toList());
    }

    @Autowired
    private GoodsRepository goodsRepository;

    public void addIndex(Long spuId) {
        // 查询spu
        SpuDTO spu = itemClient.querySpuById(spuId);
        // 构建goods
        Goods goods = buildGoods(spu);
        // 保存goods
        goodsRepository.save(goods);
    }

    public void deleteById(Long spuId) {
        goodsRepository.deleteById(spuId);
    }
}
